package com.qisimanxiang.dalaran.sentinel.tokenserver.tokenserver;

import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.server.ClusterTokenServer;
import com.alibaba.csp.sentinel.cluster.server.SentinelDefaultTokenServer;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.qisimanxiang.dalaran.sentinel.datasource.ConsulDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.consul.ConsulProperties;
import org.springframework.cloud.consul.config.ConsulConfigProperties;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;

/**
 * @author wangmeng
 * @date created in 10:58 上午 2019/9/19
 * @modified by
 */
@Slf4j
@Component
public class TokenServerConsul implements TokenServer {

    @Autowired
    private ConsulProperties properties;
    @Autowired
    private ConsulConfigProperties configProperties;

    @Value("${server.port:18730}")
    private Integer serverPort;
    @Value("${project.name:default}")
    private String projectName;
    private Integer consulWatchTime = 200;

    private static final String FLOW_POSTFIX = "flow-rules";
    private static final String PARAM_FLOW_POSTFIX = "param-flow-rules";

    public void init() {
        log.info(properties.toString());
        initClusterFlowSupplier();
        initClusterParamFlowSupplier();
        loadServerConfig();
    }

    @Override
    public void start() throws Exception {
        init();
        // 创建一个 ClusterTokenServer 的实例，独立模式 并 启动
        ClusterTokenServer tokenServer = new SentinelDefaultTokenServer();
        tokenServer.start();
    }

    /**
     * 加载namespace的集合以及ServerTransportConfig
     * 最好还要再为他们每个都注册一个SentinelProperty，这样的话可以动态的修改这些配置项
     */
    private void loadServerConfig() {
        // 加载namespace
        ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton(projectName));
        // 加载ServerTransportConfig
        ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig()
                .setIdleSeconds(600)
                .setPort(serverPort));
    }

    /**
     * 初始化集群限流的Supplier
     * 这样如果后期集群限流的规则发生变更的话，系统可以自动感知到
     */
    private void initClusterFlowSupplier() {
        // 为集群流控注册一个Supplier，该Supplier会根据namespace动态创建数据源
        ClusterFlowRuleManager.setPropertySupplier(namespace -> {
            // 使用 Nacos 数据源作为配置中心，需要在 REMOTE_ADDRESS 上启动一个 Nacos 的服务
            ReadableDataSource<String, List<FlowRule>> ds = new ConsulDataSource<>(properties.getHost(), properties.getPort(), configProperties.getPrefix() + "/" + namespace + "/" + FLOW_POSTFIX, consulWatchTime,
                    source -> {
                        log.info("更新限流规则：" + source);
                        return JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
                        });
                    });
            return ds.getProperty();
        });
    }

    /**
     * 初始化集群热点参数限流的Supplier
     * 这样如果后期集群热点参数限流的规则发生变更的话，系统可以自动感知到
     */
    public void initClusterParamFlowSupplier() {
        // 为集群热点参数流控注册一个Supplier，该Supplier会根据namespace动态创建数据源
        ClusterParamFlowRuleManager.setPropertySupplier(namespace -> {
            // 使用 Nacos 数据源作为配置中心，需要在 REMOTE_ADDRESS 上启动一个 Nacos 的服务
            ReadableDataSource<String, List<ParamFlowRule>> ds = new ConsulDataSource<>(properties.getHost(), properties.getPort(), configProperties.getPrefix() + "/" + namespace + "/" + PARAM_FLOW_POSTFIX, consulWatchTime,
                    source -> {
                        log.info("更新参数限流规则：" + source);
                        return JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {
                        });
                    });
            return ds.getProperty();
        });
    }


}
